package com.panda.core.servlet;

import com.alibaba.fastjson.JSON;
import com.panda.core.core.BaseController;
import com.panda.core.core.RestController;
import com.panda.core.core.ViewController;
import com.panda.core.mvc.ControllerHandler;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.Path;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.*;

import static java.util.Arrays.asList;
import static org.apache.commons.lang.StringUtils.replace;
import static org.apache.commons.lang.StringUtils.substringAfter;

/**
 * @Description :
 * @Author folaibu
 * @Date 2021/2/28 16:07
 */
public class DispatchServlet extends HttpServlet {
    /**
     * 请求路径与controller映射关系
     */
    private final Map<String, BaseController> controllerMapping = new HashMap<>();

    /**
     * 请求路径与映射关系缓存
     * {@link ControllerHandler}
     */
    private final Map<String, ControllerHandler> handlerMethodMapping = new HashMap<>();

    /**
     * 初始化 Servlet
     *
     * @param config
     * @throws ServletException
     */
    @Override
    public void init(ServletConfig config) throws ServletException {
        initHandlerMethods();
    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取URI  /a/hello/world
        String requestUri = req.getRequestURI();
        // 获取上下文根路径
        String contextPath = req.getContextPath();
        // 映射子路径
        String reqPath = substringAfter(requestUri, replace(contextPath, "//", "/"));
        // 映射到执行Controller
        BaseController baseController = controllerMapping.get(reqPath);
        if (baseController != null) {
            ControllerHandler controllerHandler = handlerMethodMapping.get(reqPath);
            try {
                if (controllerHandler != null) {
                    String httpMethod = req.getMethod();

                    if (!controllerHandler.getSupportedHttpMethods().contains(httpMethod)) {
                        // 找不到对应的方法支持
                        resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
                        return;
                    }

                    if (baseController instanceof ViewController) {
                        // 页面处理器
                        ViewController viewController = (ViewController) baseController;
                        // 映射到执行controller进行业务层处理
                        String viewPath = viewController.execute(req, resp);
                        // 获取Servlet上下文：ServletContext
                        ServletContext servletContext = req.getServletContext();

                        // ServletContext -> RequestDispatcher 必须以"/"开头
                        if (!viewPath.startsWith("/")) {
                            viewPath = "/" + viewPath;
                        }

                        // 将资源交由RequestDispatcher转发
                        RequestDispatcher requestDispatcher = servletContext.getRequestDispatcher(viewPath);
                        requestDispatcher.forward(req, resp);
                    } else if (baseController instanceof RestController) {
                        // TODO 直接返回json数据
                        RestController restController = (RestController) baseController;
                        Object result = restController.execute(req, resp);
                        // 设置响应头
                        resp.setContentType("application/json");
                        resp.setCharacterEncoding("UTF-8");
                        // 向响应中写入数据
                        try (PrintWriter writer = resp.getWriter()) {
                            writer.write(JSON.toJSONString(result));
                            writer.flush();
                        }
                    }
                }
            } catch (Throwable throwable) {
                if (throwable.getCause() instanceof IOException) {
                    throw (IOException) throwable.getCause();
                } else {
                    throw new ServletException(throwable.getCause());
                }
            }
        }
    }

    private void initHandlerMethods() {
        for (BaseController baseController : ServiceLoader.load(BaseController.class)) {
            Class<? extends BaseController> aClass = baseController.getClass();
            Path annotation = aClass.getAnnotation(Path.class);
            Method[] methods = aClass.getMethods();
            String reqPath = annotation.value();
            for (Method method : methods) {
                Set<String> supportedHttpMethods = findSupportedHttpMethods(method);
                Path methodAnnotation = method.getAnnotation(Path.class);
                if (methodAnnotation != null) {
                    reqPath += methodAnnotation.value();
                }
                handlerMethodMapping.put(reqPath, ControllerHandler.of(reqPath, method, supportedHttpMethods));
            }
            controllerMapping.put(reqPath, baseController);
        }
    }

    private Set<String> findSupportedHttpMethods(Method method) {
        Set<String> supports = new LinkedHashSet<>();
        for (Annotation methodAnnotation : method.getAnnotations()) {
            HttpMethod httpMethod = methodAnnotation.annotationType().getAnnotation(HttpMethod.class);
            if (httpMethod != null) {
                supports.add(httpMethod.value());
            }
        }
        if (supports.isEmpty()) {
            supports.addAll(asList(HttpMethod.GET, HttpMethod.POST, HttpMethod.PUT, HttpMethod.DELETE, HttpMethod.HEAD, HttpMethod.OPTIONS));
        }
        return supports;
    }
}
